import people from './assets/peopo.png'
import boss from './assets/boss.png'

export class ShootGame {
  constructor(props) {
    if (!props) throw new Error('请传入canvas参数')
    this.config = {
      gameGrade: 1, // 最高10
      bulletSpeed: 5, // 子弹的速度
    }
    // 生成怪物的定时器
    this.createBossInterval = null
    // 结束flag
    this.endFlag = false
    // 动画id
    this.animationID = null
    // 2d实例
    this.ctx = props.ctx
    // canvas实例
    this.canvas = props.canvas
    // canvas宽度
    this.canvasW = this.canvas.width
    // canvas高度
    this.canvasH = this.canvas.height
    // 绑定监听事件函数
    this.createAttackTask = this.createAttackTask.bind(this)
    this.addEventMousemove = this.addEventMousemove.bind(this)
    this.bindEventClickByEnd = null
    this.bindEventMousemoveByEnd = null
    // 任务配置项
    this.peopleConfig = {
      direction: 'front',
      // 根据精灵图可得知
      directionForYMap: {
        'front': 0,
        'leftSide': 32,
        'rightSide': 32,
        'back': 64,
        'run': 96,
      }
    }
    // 子弹任务列表
    this.shootTaskQueue = []
    // 怪兽任务列表
    this.bossTaskQueue = []
    // 开始初始化函数
    this.init()
  }
  // 创建人物
  createPeople () {
    const role = new RoleFactory({
      width: 52,
      height: 60,
      imageSrc: people,
      run: this.drawPeople.bind(this)
    }).roleConfig

    this.peopleConfig = {
      ...this.peopleConfig,
      ...role
    }
  }
  // 将任务画到画布上
  drawPeople () {
    const { ctx, canvasW, canvasH } = this
    const {
      directionForYMap,
      updateTime,
      direction,
      index,
      width,
      height,
      image,
    } = this.peopleConfig

    // 延迟更新小人动画, 用当前时间对比上一次的更新时间
    if (new Date().getTime() > updateTime + 150) this.peopleConfig.index++
    // 保存画布当前快照
    ctx.save()
    // 向左的时候因为没有向左的精灵图，所以自己翻转了一下
    if (direction === 'leftSide') {
      this.ctx.translate(canvasW / 2 + width / 2, (canvasH / 2 - height / 2))
      this.ctx.scale(-1, 1)
    } else this.ctx.translate((canvasW / 2 - width / 2), (canvasH / 2 - height / 2))
    // 更新画布上的image位置
    this.ctx.drawImage(image, index * 32, directionForYMap[direction], 30, 30, 0, 0, width, height)
    if (new Date().getTime() > updateTime + 150) {
      if (index >= 3) this.peopleConfig.index = 0
      this.peopleConfig.updateTime = new Date().getTime()
    }
    this.ctx.restore()
  }

  createAttackTask (e) {
    // 创建攻击任务
    let dx = e.clientX - this.canvasW / 2
    let dy = e.clientY - this.canvasH / 2
    let length = Math.sqrt(dx * dx + dy * dy)
    let unitDx = dx / length
    let unitDy = dy / length
    let farX = e.clientX + unitDx * this.canvasH / 2
    let farY = e.clientY + unitDy * this.canvasH / 2

    this.shootTaskQueue.push({
      endX: farX,
      endY: farY,
      currentX: this.canvasW / 2,
      currentY: this.canvasH / 2,
      speed: this.config.bulletSpeed,
      endFlag: false
    })
  }

  drawAttack () {
    const { bossTaskQueue, ctx } = this
    this.shootTaskQueue = this.shootTaskQueue.filter(i => !i.endFlag)
    // 循环子弹任务列表，如果与怪兽相遇就互相抵扣调
    this.shootTaskQueue.forEach(task => {
      const taskLx = task.currentX - 3
      const taskRx = task.currentX + 3
      const taskTy = task.currentY - 3
      const taskBy = task.currentY + 3
      const findIndex = bossTaskQueue.findIndex(i => {
        const BOSSLx = i.currentX
        const BOSSRx = i.currentX + i.width
        const BOSSTy = i.currentY
        const BOSSBy = i.currentY + i.height
        return  this.isIntersection(taskLx, taskRx, taskTy, taskBy, BOSSLx, BOSSRx, BOSSTy, BOSSBy)
      })

      // 说明打到了， 需要清空子弹以及boss
      if (findIndex > -1) {
        task.endFlag = true
        bossTaskQueue[findIndex].endFlag = true
      }

      // 计算子弹的移动方向和距离
      let dx = task.endX - task.currentX
      let dy = task.endY - task.currentY
      let distance = Math.sqrt(dx * dx + dy * dy)

      if (distance > task.speed) {
        // 移动子弹
        task.currentX += (dx / distance) * task.speed
        task.currentY += (dy / distance) * task.speed
      } else {
        task.endFlag = true
      }

      ctx.beginPath()
      ctx.arc(task.currentX, task.currentY, 3, 0, Math.PI * 2)
      ctx.fillStyle = 'red'
      ctx.fill()
    })
  }

  /**
   * 创建怪兽插入到怪兽列表
   */
  createBoss () {
    this.createBossInterval = setInterval(() => {
      const bossConfig = new RoleFactory({
        width: 52,
        height: 60,
        imageSrc: boss
      }).roleConfig
      bossConfig.endX = this.canvasW / 2 - bossConfig.width / 2
      bossConfig.endY = this.canvasH / 2 - bossConfig.height / 2
      bossConfig.currentX = Math.random() * this.canvasW
      bossConfig.currentY = 0.5 > Math.random() ? Math.random() * 100 : (Math.random() * (this.canvasH  - (this.canvasH - 100) + 1)) + (this.canvasH - 100)
      bossConfig.speed = this.config.gameGrade * 2
      bossConfig.image.onload = () => {
        this.bossTaskQueue.push(bossConfig)
      }
    }, 1000)
  }

  drawBoss () {
    const { ctx } = this
    this.bossTaskQueue = this.bossTaskQueue.filter(i => !i.endFlag)
    this.bossTaskQueue.forEach(bossItem => {
      ctx.save()
      if (new Date().getTime() > bossItem.updateTime + 100) {
        bossItem.index++
      }
      ctx.drawImage(bossItem.image, bossItem.index * 64, 0, 60, 60, bossItem.currentX, bossItem.currentY, bossItem.width, bossItem.height)
      if (new Date().getTime() > bossItem.updateTime + 100) {
        if (bossItem.index >= 31) {
          bossItem.index = 0
        }
        bossItem.updateTime = new Date().getTime()
      }
      const dx = bossItem.endX - bossItem.currentX
      const dy = bossItem.endY - bossItem.currentY
      const distance = Math.sqrt(dx * dx + dy * dy)
      if (distance > bossItem.speed) {
        // 移动怪兽
        bossItem.currentX += (dx / distance) * bossItem.speed
        bossItem.currentY += (dy / distance) * bossItem.speed
      } else {
        // 安全走到终点, 游戏结束
        this.end()
      }
      ctx.restore()
    })
  }

  // 重置画布
  resetCanvas () {
    this.ctx.clearRect(0, 0, this.canvasW, this.canvasH)
  }

  /**
   * @description 判断是否交集
   * @param leftA A左边距左距离
   * @param rightA A右边距左距离
   * @param topA A上边距上距离
   * @param bottomA A下边距上距离
   * @param leftB B的
   * @param rightB B的
   * @param topB B的
   * @param bottomB B的
   * @return {boolean}
   */
  isIntersection (leftA, rightA, topA, bottomA, leftB, rightB, topB, bottomB) {
    // leftA > rightB  物体A的左X轴位置，比物体B的右X位置还大，证明A 完全在B的右面, 反之，完全在左面
    // topA > bottomB  物体A的上Y轴位置，比物体B的下Y位置还大，证明A 完全在B的下面, 反之，完全在上面
    // 只要满足以下四个条件中的一个，即可断定没有任何交集
    return !(leftA > rightB || rightA < leftB || topA > bottomB || bottomA < topB);
  }

  drawEndStyle () {
    const { ctx, canvasW, canvasH } = this
    let width = 280
    let height = 160
    let x = canvasW / 2 - width / 2
    let y = canvasH / 2 - height / 2

    let cornerRadius = 12

    // 绘制路径
    ctx.beginPath()
    ctx.moveTo(x + cornerRadius, y)
    ctx.lineTo(x + width - cornerRadius, y)
    ctx.arcTo(x + width, y, x + width, y + cornerRadius, cornerRadius)
    ctx.lineTo(x + width, y + height - cornerRadius)
    ctx.arcTo(x + width, y + height, x + width - cornerRadius, y + height, cornerRadius)
    ctx.lineTo(x + cornerRadius, y + height)
    ctx.arcTo(x, y + height, x, y + height - cornerRadius, cornerRadius)
    ctx.lineTo(x, y + cornerRadius)
    ctx.arcTo(x, y, x + cornerRadius, y, cornerRadius)
    ctx.closePath()

    // 设置样式并绘制
    const gradient = ctx.createLinearGradient(0, 0, 0, canvasH)
    gradient.addColorStop(0, 'rgba(0,0,0,1)')
    gradient.addColorStop(1, 'rgba(97,94,94,1)')
    ctx.fillStyle = gradient
    ctx.fill()

    ctx.font = "18px sans-serif"
    ctx.fillStyle = '#fff'
    ctx.fillText("游戏结束", canvasW / 2 - 18 * 2, canvasH / 2 - 24)

    ctx.font = "18px sans-serif"
    ctx.fillStyle = '#fff'
    ctx.fillText("重新开始", canvasW / 2 + 32, y + height - 12)
    this.bindEventClickByEnd = this.eventClickByEnd.bind(
        this,
        canvasW / 2 + 32,
        canvasW / 2 + 32 + 76,
        y + height - 32,
        y + height - 32 + 22,
    )
    this.bindEventMousemoveByEnd = this.eventMousemoveByEnd.bind(
        this,
        canvasW / 2 + 32,
        canvasW / 2 + 32 + 76,
        y + height - 32,
        y + height - 32 + 22,
    )
    window.addEventListener('click', this.bindEventClickByEnd)
    window.addEventListener('mousemove', this.bindEventMousemoveByEnd)
  }

  // game end
  end () {
    this.endFlag = true
    clearInterval(this.createBossInterval)
    this.createBossInterval = null
    // 清除事件监听
    window.removeEventListener('mousemove', this.addEventMousemove)
    window.removeEventListener('click', this.createAttackTask)
    cancelAnimationFrame(this.animationID)
    // 结束函数
    this.drawEndStyle()
  }

  eventClickByEnd (leftX, rightX, topY, bottomY, { clientX, clientY }) {
    if (this.isIntersection(clientX, clientX, clientY, clientY, leftX, rightX, topY, bottomY)) {
      this.bossTaskQueue = []
      this.shootTaskQueue = []
      window.removeEventListener('click', this.bindEventClickByEnd)
      window.removeEventListener('mousemove', this.bindEventMousemoveByEnd)
      console.log('触发了一次')
      this.init()
    }
  }

  eventMousemoveByEnd (leftX, rightX, topY, bottomY, { clientX, clientY }) {
    if (this.isIntersection(clientX, clientX, clientY, clientY, leftX, rightX, topY, bottomY)) {
      this.canvas.style.cursor = 'pointer'
    } else {
      this.canvas.style.cursor = 'default'
    }
  }

  addEventMousemove (e) {
    const y = e.clientY
    const x = e.clientX
    // 背面
    if (y < (this.canvasH / 2) && x > (this.canvasW / 4) && x < (this.canvasW / 4) * 3) {
      this.peopleConfig.direction = 'back'
      return
    }
    // 正面
    if (y > (this.canvasH / 2) && x > (this.canvasW / 4) && x < (this.canvasW / 4) * 3) {
      this.peopleConfig.direction = 'front'
      return
    }
    // 左面
    if (x < (this.canvasW / 2)) {
      this.peopleConfig.direction = 'leftSide'
      return
    }
    // 右面
    if (x > (this.canvasW / 2)) {
      this.peopleConfig.direction = 'rightSide'
    }
  }

  init () {
    this.resetCanvas()
    this.config.gameGrade = Math.floor(Math.random() * (10 - 1 + 1)) + 1
    this.endFlag = false
    // 监听鼠标移动，控制小人的头转方向
    window.addEventListener('mousemove', this.addEventMousemove)
    // 监听click函数，控制子弹发射
    window.addEventListener('click', this.createAttackTask)

    // 根据工厂，创建一个主人公
    this.createPeople()
    // 创建boss
    this.createBoss()

    const executeAnimation = () => {
      if (this.endFlag) return
      this.resetCanvas()
      this.drawPeople()
      this.drawBoss()
      this.drawAttack()
      this.animationID = requestAnimationFrame(executeAnimation)
    }
    this.animationID = requestAnimationFrame(executeAnimation)
  }
}

/**
 * 工厂函数，创建人物/角色/boss
 */
class RoleFactory {
  constructor({ width, height, imageSrc, run }) {
    this.roleConfig = {
      width,
      height,
      imageSrc,
      run
    }
    this.create()
  }
  create () {
    const image = new Image()
    image.src = this.roleConfig.imageSrc
    image.onload = this.roleConfig.run
    this.roleConfig = {
      ...this.roleConfig,
      updateTime: new Date().getTime(),
      image: image,
      index: 0,
      endFlag: false
    }
  }
}
